#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <sqlite3.h>
#import <UIKit/UIApplication.h>
#import <EventKit/EKEvent.h>
#import <EventKit/EKRecurrenceRule.h>
#import <SpringBoard/SBApplicationController.h>
#import <SpringBoard/SBApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWApplication.h"
#import "framework/FWPreferences.h"

#import "PICommon.h"
#import "PIIntegration.h"
#import "PIPlugin.h"
#import "PIContext.h"
#import "PIContextObject.h"


NSString* const PIIntegrationContextPasteboard = @"net.webis.pocketinformant.contextLocations";

NSString* const PIContextFieldNameTitle = @"title"; 
NSString* const PIContextFieldNameKey = @"pk"; 

NSString* const PIiPhoneIdentifier = @"pocketinformant";
NSString* const PIiPadIdentifier = @"pocketinformantiPad";

static NSString* const kAttrColorTop = @"ColTop";
static NSString* const kAttrColorHigh = @"ColHigh";
static NSString* const kAttrColorNormal = @"ColNorm";
static NSString* const kAttrColorLow = @"ColLow";
static NSString* const kAttrColorNone = @"ColNone";
static NSString* const kAttrColorOverdue = @"ColOver";

static NSString* const kAttrColorBGTop = @"ColBGTop";
static NSString* const kAttrColorBGHigh = @"ColBGHigh";
static NSString* const kAttrColorBGNormal = @"ColBGNorm";
static NSString* const kAttrColorBGLow = @"ColBGLow";
static NSString* const kAttrColorBGNone = @"ColBGNone";

static NSString* const kAttrTaskMode = @"TaskMode";

static NSString* const kPILocationsEnabled = @"PISignificantLocationChangeMonitoring";


static NSString* const kFieldNameContextStatus = @"xxx_contextstat";
static NSString* const kFieldNameChildCount = @"xxx_children";

static NSInteger const kSQLArrayOfCalendars = 1;
static NSInteger const kSQLArrayOfTags = 2;


// Names defined in the PI2 database
NSString* const kPIEntityTypeTask = @"PICalTask";
NSString* const kPIEntityTypeTaskTemplate = @"PICalTaskTemplate";
NSString* const kPIEntityTypeEvent = @"PICalEvent";
NSString* const kPIEntityTypeEventOccurance = @"PICalEventOccurrence";
NSString* const kPIEntityTypeEventTemplate = @"PICalEventTemplate";
NSString* const kPIEntityTypeAttendee = @"PICalAttendee";
NSString* const kPIEntityTypeContext = @"PIContext";
NSString* const kPIEntityTypeProject = @"PIProject";
NSString* const kPIEntityTypeProjectInternal = @"PIInternalProject";
NSString* const kPIEntityTypeTag = @"PITag";
NSString* const kPIEntityTypeCalendar = @"PICalCalendar";
NSString* const kPIEntityTypeCalendarInternal = @"PIInternalCalendar";
NSString* const kPIEntityTypeNote = @"PICalNote";

NSInteger const kPIEntityIDInternalTaskCalendar = 0;
NSInteger const kPIEntityIDInternalNotesCalendar = 1;


@implementation PIIntegration


// ******************************************************************************************
#pragma mark -
#pragma mark Colors from Pocket Informant

static UIColor* PIColorFromPrefs(NSDictionary* prefs, NSString* key, double defRed, double defGreen, double defBlue)
{
    if (NSArray* color = [prefs valueForKey:key])
        return [UIColor colorWithRed:[[color objectAtIndex:0] doubleValue] 
                               green:[[color objectAtIndex:1] doubleValue]
                                blue:[[color objectAtIndex:2] doubleValue]
                               alpha:[[color objectAtIndex:3] doubleValue]];
    else
        return [UIColor colorWithRed:defRed
                               green:defGreen
                                blue:defBlue
                               alpha:1.0];	
}

+ (void)updateInfoFromPrefs:(NSDictionary*)prefs application:(FWApplication*)application 
{
    [application setAttribute:kAttrColorNone value:PIColorFromPrefs(prefs, @"PICalPriorityColor_0_text", 0, 0, 0)];
    [application setAttribute:kAttrColorTop value:PIColorFromPrefs(prefs, @"PICalPriorityColor_1_text", 1, 1, 1)];
    [application setAttribute:kAttrColorHigh value:PIColorFromPrefs(prefs, @"PICalPriorityColor_5_text", 1, 1, 1)];
    [application setAttribute:kAttrColorNormal value:PIColorFromPrefs(prefs, @"PICalPriorityColor_10_text", 0, 0, 0)];
    [application setAttribute:kAttrColorLow value:PIColorFromPrefs(prefs, @"PICalPriorityColor_15_text", 0, 0, 0)];

    [application setAttribute:kAttrColorBGNone value:PIColorFromPrefs(prefs, @"PICalPriorityColor_0_background", 0.247058823529412, 0.247058823529412, 0.247058823529412)];
    [application setAttribute:kAttrColorBGTop value:PIColorFromPrefs(prefs, @"PICalPriorityColor_1_background", 1, 0, 0)];
    [application setAttribute:kAttrColorBGHigh value:PIColorFromPrefs(prefs, @"PICalPriorityColor_5_background", 1, 0.4980392156862745, 0)];
    [application setAttribute:kAttrColorBGNormal value:PIColorFromPrefs(prefs, @"PICalPriorityColor_10_background", 0, 1, 0)];
    [application setAttribute:kAttrColorBGLow value:PIColorFromPrefs(prefs, @"PICalPriorityColor_15_background", 0, 1, 1)];
    
    [application setAttribute:kAttrColorOverdue value:PIColorFromPrefs(prefs, @"PIColorTaskOverdue", 1, 0.4470588235294118, 0.4470588235294118)];

    if (NSNumber* taskMode = [prefs valueForKey:@"PITaskMode"])
        [application setAttribute:kAttrTaskMode value:taskMode];
    else
        [application setAttribute:kAttrTaskMode value:[NSNumber numberWithInt:PITaskModeToodledo]];
}

+ (UIColor*)colorForOverdue:(FWApplication*)application 
{
    return (UIColor*)[application getAttribute:kAttrColorOverdue];
}    

+ (UIColor*)colorForAction:(int)action application:(FWApplication*)application 
{
    if (action <= PIActionNone)
        return (UIColor*)[application getAttribute:kAttrColorNone];
    else if (action <= PIActionPlanning)
        return (UIColor*)[application getAttribute:kAttrColorHigh];
    else if (action <= PIActionSomeday)
        return (UIColor*)[application getAttribute:kAttrColorNormal];
    else
        return (UIColor*)[application getAttribute:kAttrColorLow];
}

+ (UIColor*)colorBGForAction:(int)action application:(FWApplication*)application 
{
    if (action <= PIActionNone)
        return (UIColor*)[application getAttribute:kAttrColorBGNone];
    else if (action <= PIActionPlanning)
        return (UIColor*)[application getAttribute:kAttrColorBGHigh];
    else if (action <= PIActionSomeday)
        return (UIColor*)[application getAttribute:kAttrColorBGNormal];
    else
        return (UIColor*)[application getAttribute:kAttrColorBGLow];
}

+ (UIColor*)colorForImportance:(int)importance application:(FWApplication*)application 
{
    if (importance <= PIImportanceNone)
        return (UIColor*)[application getAttribute:kAttrColorNone];
    else if (importance <= PIImportanceTop)
        return (UIColor*)[application getAttribute:kAttrColorTop];
    else if (importance <= PIImportanceHigh)
        return (UIColor*)[application getAttribute:kAttrColorHigh];
    else if (importance <= PIImportanceNormal)
        return (UIColor*)[application getAttribute:kAttrColorNormal];
    else
        return (UIColor*)[application getAttribute:kAttrColorLow];
}

+ (UIColor*)colorBGForImportance:(int)importance application:(FWApplication*)application 
{
    if (importance <= PIImportanceNone)
        return (UIColor*)[application getAttribute:kAttrColorBGNone];
    else if (importance <= PIImportanceTop)
        return (UIColor*)[application getAttribute:kAttrColorBGTop];
    else if (importance <= PIImportanceHigh)
        return (UIColor*)[application getAttribute:kAttrColorBGHigh];
    else if (importance <= PIImportanceNormal)
        return (UIColor*)[application getAttribute:kAttrColorBGNormal];
    else
        return (UIColor*)[application getAttribute:kAttrColorBGLow];
}


// ******************************************************************************************
#pragma mark -

+ (NSString*)stringForTitle:(NSString*)title withIcon:(NSString*)icon withProjectIcon:(NSString*)projectIcon
{
    if (projectIcon && (projectIcon.length > 0)) {
        if (icon && (icon.length > 0))
            title = [title stringByAppendingFormat:@" %@%@", projectIcon, icon];
        else
            title = [title stringByAppendingFormat:@" %@", projectIcon];
    }
    else if (icon && (icon.length > 0))
        title = [title stringByAppendingFormat:@" %@", icon];

    return title;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Internal names from Pocket Informant values

static NSDictionary* const nameActionLookup = [[NSDictionary alloc] initWithObjectsAndKeys:
                                               @"Action/Next Action", FWNewNumberWithInt(PIActionNext),
                                               @"Action/Active", FWNewNumberWithInt(PIActionActive),
                                               @"Action/Planning", FWNewNumberWithInt(PIActionPlanning),
                                               @"Action/Delegated", FWNewNumberWithInt(PIActionDelegated),
                                               @"Action/Waiting", FWNewNumberWithInt(PIActionWaiting),
                                               @"Action/Hold", FWNewNumberWithInt(PIActionHold),
                                               @"Action/Postponed", FWNewNumberWithInt(PIActionPostponed),
                                               @"Action/Someday", FWNewNumberWithInt(PIActionSomeday),
                                               @"Action/Cancelled", FWNewNumberWithInt(PIActionCancelled),
                                               @"Action/Reference", FWNewNumberWithInt(PIActionReference),
                                               nil];

+(NSString*) nameForAction:(PIActionType)action
{
    NSString* name = [nameActionLookup objectForKey:[NSNumber numberWithInt:action]];
    return name ? name : @"Action/None";
}

static NSDictionary* const nameImportanceLookup = [[NSDictionary alloc] initWithObjectsAndKeys:
                                                   @"Importance/Top", FWNewNumberWithInt(PIImportanceTop),
                                                   @"Importance/High", FWNewNumberWithInt(PIImportanceHigh),
                                                   @"Importance/Normal", FWNewNumberWithInt(PIImportanceNormal),
                                                   @"Importance/Low", FWNewNumberWithInt(PIImportanceLow),
                                                   nil];

+(NSString*)nameForImportance:(PIImportanceType)importance
{
    NSString* name = [nameImportanceLookup objectForKey:[NSNumber numberWithInt:importance]];
    return name ? name : @"Importance/None";
}


// ******************************************************************************************
#pragma mark -
#pragma mark Paths to items within the Pocket Informant application

+ (NSString*)iconFileForAction:(int)action
{
    switch (action) {
        case PIActionNext:
            return @"NextAction";
        case PIActionActive:
            return @"Active";
        case PIActionPlanning:
            return @"Planning";
        case PIActionDelegated:
            return @"Delegated";
        case PIActionWaiting:
            return @"Waiting";
        case PIActionHold:
            return @"Hold";
        case PIActionPostponed:
            return @"Postponed";
        case PIActionSomeday:
            return @"Someday";
        case PIActionCancelled:
            return @"Cancelled";
        case PIActionReference:
            return @"Reference";
        default:
            return nil;
    }
}

+ (NSString*)iconPathForAction:(int)action application:(FWApplication*)application 
{
    if (NSString* icon = [PIIntegration iconFileForAction:action])
        return [application.applicationPath stringByAppendingFormat:@"/%@.png", icon];
    else
        return nil;
}

+ (NSString*)iconPathForSmallFlag:(FWApplication*)application
{
    return [application.applicationPath stringByAppendingString:@"/star_yellow_small.png"];
}

+ (NSString*)iconPathForLargeFlag:(FWApplication*)application
{
    return [application.applicationPath stringByAppendingString:@"/star_yellow.png"];
}

+ (NSString*)iconPathForRepeat:(FWApplication*)application
{
    if (((PIPlugin*)application.fwPlugin).isPocketInformantPresent)
        return [application.applicationPath stringByAppendingString:@"/recurrence_icon.png"];
    else
        return [[NSBundle bundleForClass:[self class]] pathForResource:@"repeat" ofType:@"png"];
}

+ (NSString*)iconPathForAttendees:(FWApplication*)application
{
    if (((PIPlugin*)application.fwPlugin).isPocketInformantPresent)
        return [application.applicationPath stringByAppendingString:@"/meeting_icon.png"];
    else
        return [[NSBundle bundleForClass:[self class]] pathForResource:@"attendees" ofType:@"png"];
}

+ (NSString*)iconPathForReminder:(FWApplication*)application
{
    if (((PIPlugin*)application.fwPlugin).isPocketInformantPresent)
        return [application.applicationPath stringByAppendingString:@"/alarm_icon.png"];
    else
        return [[NSBundle bundleForClass:[self class]] pathForResource:@"reminder" ofType:@"png"];
}

+ (NSString*)iconPathForParent:(FWApplication*)application
{
    return [application.applicationPath stringByAppendingString:@"/tree_parent_icon.png"];
}

+ (NSString*)iconPathForChild:(FWApplication*)application
{
    return [application.applicationPath stringByAppendingString:@"/tree_icon.png"];
}

+ (PITaskMode)taskMode:(FWApplication*)application
{
    NSNumber* mode = [application getAttribute:kAttrTaskMode];
    if (mode)
        return (PITaskMode)[mode intValue];
    else
        return PITaskModeToodledo;
}


static NSString* kDefaultAlarm = @"0:0";
static NSString* kDefaultAlarmFile = @"alarm.caf";

+ (NSURL*)alarmSoundURL:(FWApplication*)application alarm:(NSString*)alarm
{
    NSString* locn = [alarm substringToIndex:1];
    alarm = [alarm substringFromIndex:2];
    
    NSBundle* bundle;
    
    if ([locn isEqualToString:@"0"]) {
        bundle = [NSBundle bundleWithPath:@"/System/Library/Audio/UISounds"];
#ifdef DEVELOPMENT_MODE
        bundle = [NSBundle bundleForClass:[self class]];
#endif
    } 
    else {
        bundle = [NSBundle bundleWithPath:application.applicationPath];
    }
    
    if ([alarm isEqualToString:@"0"])
        alarm = kDefaultAlarmFile;
    
    NSRange seperator = [alarm rangeOfString:@"."];
    NSString* sound = [alarm substringToIndex:seperator.location];
    NSString* type = [alarm substringFromIndex:seperator.location + 1];
    
    if (NSString* filePath = [bundle pathForResource:sound ofType:type]) {
        NSURL* alarmSound = [NSURL fileURLWithPath:[bundle pathForResource:sound ofType:type]];
        XLog(@"LI:PICal: Alarm sound file URL = %@", alarmSound);
        
        return alarmSound;
    }
    else
        return [self alarmSoundURL:application alarm:kDefaultAlarm];
}

// ******************************************************************************************
#pragma mark -
#pragma mark Location-based context handling

+ (BOOL)locationMonitoringForPrefs:(NSDictionary*)prefs 
{
    // Unless we know explicitly that location monitoring is off then assume it's on
    if (NSNumber* enabled = [prefs objectForKey:kPILocationsEnabled])
        return [enabled boolValue];
    else
        return YES;
}

+ (NSArray*)locationContextsFromPI 
{
    UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:PIIntegrationContextPasteboard create:NO];
    NSMutableArray* result = [NSMutableArray arrayWithCapacity:10];
    
    if (pasteBoard) {
        NSArray* items = [pasteBoard items];
        for (NSDictionary* item in items) {
            DebugLog(@"PI:FWCommon: Context from PI %@", item);
            
            NSData* titleData = [item objectForKey:@"title"];
            NSString* title = [[[NSString alloc] initWithData:titleData encoding:NSASCIIStringEncoding] autorelease];

            // Primary key
            NSInteger pk = 0;

            // Test for PI1 format number
            if (NSData* pkData = [item objectForKey:@"pk"]) {
                if (NSNumber* pkNumber = [NSPropertyListSerialization propertyListWithData:pkData options:0 format:NULL error:NULL])
                    pk = [pkNumber intValue];
            }

            // Test for PI2 format number
            if (NSData* pkRefId = [item objectForKey:@"refID"]) {
                if (NSString* refId = [NSPropertyListSerialization propertyListWithData:pkRefId options:0 format:NULL error:NULL])
                    pk = FWKeyFromRefID(refId);
            }

            if (title && (pk > 0)) {
                // Do we already have this value? Sometimes PI sends the same one twice!
                BOOL exists = NO;
                for (PIContext* test in result) {
                    // If we already have this value then skip it and move to the next
                    if (test.key == pk) {
                        exists = YES;
                        break;
                    }
                }
                
                if (!exists)
                    [result addObject:[PIContext contextForKey:pk title:title selected:YES]];
            }
        }
    }

    XLog(@"LI:PITodo: Updated location contexts from PI");
    DebugLog(@"LI:PITodo: New contexts = %@", result);

    return result;
}

+ (NSArray*)arrayOfCalendars:(FWApplication*)application includeNative:(BOOL)native includeInternal:(BOOL)internal
{  
    PIPlugin* plugin = (PIPlugin*)application.fwPlugin;
    
    NSString* sql;
    if (plugin.isPocketInformant2)
        sql = [NSString stringWithFormat:
               @"select 2, C.Z_PK, C.ZTITLE, C.ZCOLOR, C.ZICON"
               " from ZPICALCALENDAR C"
               " join Z_PRIMARYKEY Z on C.Z_ENT=Z.Z_ENT"
               " where (Z.Z_NAME='%@' or (Z.Z_NAME='%@' and C.ZINTERNALID=%d))"
               " order by ZTITLE",
               kPIEntityTypeCalendar,
               kPIEntityTypeCalendarInternal,
               internal ? kPIEntityIDInternalTaskCalendar : -1];
    else
        sql = @"select 1, id, title, color, icon from calendar order by title";
    
    PIIntegration* exec = [[PIIntegration alloc] init];
    NSArray* calendars = FWDBGetRecords(sql, kSQLArrayOfCalendars, nil, application.databasePath, application, exec, NO);
    [exec release];

    if (calendars)
        return calendars;
    else
        return [NSArray array];
}

- (NSObject*)sqlArrayOfCalendars:(sqlite3_stmt *)sqlStatement
{
    NSInteger version = sqlite3_column_int(sqlStatement, 0);
    NSInteger key = sqlite3_column_int(sqlStatement, 1);
    NSString* title = FWDBReadColumnString(sqlStatement, 2);
    //const char *cColor = (const char*)sqlite3_column_text(sqlStatement, 3);
    NSString* icon = FWDBReadColumnString(sqlStatement, 4);
    
    //NSString* color =  toUTF8NSString(cColor);
    
    return [PIContextObject objectWithKey:key
                                     name:title
                                     icon:icon
                                    color:[UIColor redColor]
                                   native:NO];
}

+ (NSArray*)arrayOfTags:(FWApplication*)application keys:(NSArray*)keys database:(FWDatabase*)database
{
    PIPlugin* plugin = (PIPlugin*)application.fwPlugin;

    NSString* sql;
    if (plugin.isPocketInformant2)
        sql = @"select 2, Z_PK, ZTITLE, ZCOLOR, ZICON from ZPITAG order by ZTITLE";
    else
        sql = @"select 1, id, title, color, icon from tag order by title";
    
    if (keys) {
        NSMutableString* filter = [NSMutableString stringWithCapacity:keys.count * 3];
        for (NSString* key in keys) {
            [filter appendString:key withSeparator:@","];
        }

        if (filter.length > 0) {
            if (plugin.isPocketInformant2)
                sql = [sql stringByAppendingFormat:@" where Z_PK in (%@)", filter];
            else
                sql = [sql stringByAppendingFormat:@" where id in (%@)", filter];
        }
    }
    
    PIIntegration* exec = [[PIIntegration alloc] init];
    NSArray* tags = FWDBGetRecordsFromConnection(sql, kSQLArrayOfTags, nil, database, application, exec, NO);
    [exec release];

    if (tags)
        return tags;
    else
        return [NSArray array];
}

+ (NSArray*)arrayOfTags:(FWApplication*)application keys:(NSArray*)keys
{  
    FWDatabase* database = FWDBConnectDatabase(application.databasePath, application);
    @try {
        return [PIIntegration arrayOfTags:application keys:keys database:database];
    }
    @finally {
        FWDBDisconnectDatabase(database);
    }
}

- (NSObject*)sqlArrayOfTags:(sqlite3_stmt *)sqlStatement
{
    NSInteger version = sqlite3_column_int(sqlStatement, 0);
    NSInteger key = sqlite3_column_int(sqlStatement, 1);
    NSString* title = FWDBReadColumnString(sqlStatement, 2);
    NSString* icon = FWDBReadColumnString(sqlStatement, 4);
    
    UIColor* color = [UIColor clearColor];
    if (version == 2)
        color = FWDBReadColumnBlobWithDefault(sqlStatement, 3, color);
    
    return [PIContextObject objectWithKey:key
                                     name:title
                                     icon:icon
                                    color:color
                                   native:YES];
}

+ (PIRecurrance*)recurrenceWithFrequency:(NSInteger)type interval:(NSInteger)interval daysOfTheWeek:(NSArray*)daysOfWeek daysOfTheMonth:(NSArray*)daysOfMonth monthsOfTheYear:(NSArray*)monthsOfYear occurances:(NSInteger)occurances endDate:(NSDate*)endDate fromDate:(PIRecurranceFromDateType)fromDate
{
    @try {
        EKRecurrenceEnd* end;
        if (occurances > 0)
            end = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount:occurances];
        else if (endDate.timeIntervalSinceReferenceDate > 0)
            end = [EKRecurrenceEnd recurrenceEndWithEndDate:endDate];
        else
            end = [EKRecurrenceEnd recurrenceEndWithOccurrenceCount:-1];
        
        NSMutableArray* piDaysOfWeek = [NSMutableArray arrayWithArray:daysOfWeek];
        for (NSInteger i = 0; i < piDaysOfWeek.count; ++i)
            [piDaysOfWeek replaceObjectAtIndex:i withObject:[EKRecurrenceDayOfWeek dayOfWeek:[[piDaysOfWeek objectAtIndex:i] intValue]]];
        
        EKRecurrenceRule* rule = [[[EKRecurrenceRule alloc] initRecurrenceWithFrequency:(EKRecurrenceFrequency)type interval:interval daysOfTheWeek:piDaysOfWeek daysOfTheMonth:daysOfMonth monthsOfTheYear:monthsOfYear weeksOfTheYear:nil daysOfTheYear:nil setPositions:nil end:end] autorelease];
        
        return [PIRecurrance recurranceWithRule:rule fromDate:fromDate];
    }
    @catch (NSException *exception) {
        // Didn't like the data
        return nil;
    }
}

// ******************************************************************************************
#pragma mark -
#pragma mark Database support

- (NSObject*)objectForSQLRecord:(sqlite3_stmt *)sqlStatement withId:(NSInteger)sqlid context:(NSObject*)context application:(FWApplication *)application
{
    if (sqlid == kSQLArrayOfCalendars)
        return [self sqlArrayOfCalendars:sqlStatement];
    else if (sqlid == kSQLArrayOfTags)
        return [self sqlArrayOfTags:sqlStatement];
    else
        return nil;
}

@end
